/*
 * biller.cpp
 *
 *  Created on: Dec 18, 2013
 *      Author: killerg
 */

#include "server_deps.h"
#include "bill_session.h"
#include "server.h"
#include "server_cfg.h"
#include "server_console.h"
#include "game_logic/player_mgr.h"
#include "game_logic/player_data.h"
#include "game_logic/proto_mgr.h"
#include "game_logic/mail_mgr.h"
#include "game_logic/game_cfg.h"
#include "game_logic/chat_mgr.h"

#include "share/msg.h"
#include "share/clock.h"
#include "share/sl_msg.pb.h"

#include "../../version.h"

#include <fcntl.h>

void handle_blogon(sb::BMsg* msg) {
    const auto &logon = msg->logon();
    g_bill->authed = logon.ok();
}

void handle_border(sb::BMsg* msg) {
    const auto &order = msg->order();
    for (int i=0; i<order.order_size(); ++i) {
    	const auto &oorder = order.order(i);

        int64_t playerid = oorder.playerid() + int64_t(g_cfg->ServerId) * co::MaxAccountNum;
        double payfee = oorder.payfee();
        const string &orderId = oorder.orderid();
        int32_t splayerid = oorder.playerid();
        string mc = oorder.machinecode();

        g_playermgr->player_load(
                playerid,
                [mc, payfee, orderId, splayerid](sp_player_t player){
        			if (!player) {
                        UTIL_LOG(E,
                                "err bill order:\"bill:%d:%s\" player not exist",
                                g_cfg->ServerId,
                                orderId.c_str());
                        return;
                    }

                    Json::Value order;
                    order["objtype"] = "order";
                    order["serverid"] = g_cfg->ServerId;
                    order["orderid"] = orderId;
                    order["playerid"] = splayerid;
                    order["payfee"] = payfee;
                    order["mc"] = mc;
                    order["dealt"] = true;

                    g_bill->log_order(orderId.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), payfee, "set_dealt");
                    g_bill->db.set(
                            fmt_cstr("order:%d:%s", g_cfg->ServerId, orderId.c_str()),
                            order,
                            [mc, player, payfee, orderId](db_error_t err){
                                switch (err) {
                                case DBE_Success:
                                {
                                	int32_t feeid = int32_t(payfee);
                                    const tagProtoCharge* proto = MAProto(Charge, feeid);
                                    if (NULL == proto) {
                                        UTIL_LOG(E,
                                                "player:%d, can not find ChargeProto:%d",
                                                player->id,
                                                feeid
                                                );
                                        break;
                                    }

                                	int32_t charge_times = player->att(Pl4s_ChargeTimes);
                                    int64_t money = player->att(cs::Pl8s_Money);
                                    int32_t day_acc = player->att(cs::Pl4s_DayAccPayFee);
                                    int32_t week_acc = player->att(cs::Pl4s_WeekAccPayFee);
                                    int64_t acc = player->att(cs::Pl8s_AccPayFee);

                                    money += proto->money;
                                    day_acc += payfee;
                                    week_acc += payfee;
                                    acc += payfee;

                                    if (charge_times <= 0)
                                    	money += payfee * 10 * 0.5;

                                    player->att(cs::Pl8s_Money, money);
                                    player->att(cs::Pl4s_DayAccPayFee, day_acc);
                                    player->att(cs::Pl4s_WeekAccPayFee, week_acc);
                                    player->att(cs::Pl8s_AccPayFee, acc);

                                    int32_t day_reward_idx = player->att(cs::Pl4s_DayAccPayRewardId);
                                    int32_t week_reward_idx = player->att(cs::Pl4s_WeekAccPayRewardId);

                                    for (const auto &proto : g_protomgr->_arrProtoAccChargeReward) {
                                        switch (proto.type) {
                                        case 0:
                                            if (day_acc >= proto.fee && day_reward_idx < proto.id) {
                                                day_reward_idx = proto.id;
                                                g_mail->send_system(
                                                        player->id,
                                                        EMT_ChargeReward,
                                                        "",
                                                        proto.rewardid,
                                                        1.0f,
                                                        [player, &proto](bool ok){
                                                            if (!ok) {
                                                                UTIL_LOG(E,
                                                                        "player:%d, dayaccreward:%d not sended",
                                                                        player->id,
                                                                        proto.id
                                                                        );
                                                            }
                                                        });
                                            }
                                            break;
                                        case 1:
                                            if (week_acc >= proto.fee && week_reward_idx < proto.id) {
                                                week_reward_idx = proto.id;
                                                g_mail->send_system(
                                                        player->id,
                                                        EMT_ChargeReward,
                                                        fmt_cstr("%d", proto.fee),
                                                        proto.rewardid,
                                                        1.0f,
                                                        [player, &proto](bool ok){
                                                            if (!ok) {
                                                                UTIL_LOG(E,
                                                                        "player:%d, weekaccreward:%d not sended",
                                                                        player->id,
                                                                        proto.id
                                                                        );
                                                            }
                                                        });
                                            }
                                            break;
                                        case 2:
                                        	if (payfee == proto.fee) {
                                        		item_t* key = NULL;
                                        		player->bag.do_while([payfee, &key](int serial, item_t* &item)->bool{
                                        			if (item->proto->type == EIT_Key && item->proto->misc1 == payfee) {
                                        				key = item;
                                        				return false;
                                        			}
                                        			return true;
                                        		});
                                        		if (key) {
                                                    g_mail->send_system(
                                                            player->id,
                                                            EMT_ChargeReward,
                                                            fmt_cstr("%d", proto.fee),
                                                            key->proto->misc2,
                                                            1.0f,
                                                            [player, &proto](bool ok){
                                                                if (!ok) {
                                                                    UTIL_LOG(E,
                                                                            "player:%d, weekaccreward:%d not sended",
                                                                            player->id,
                                                                            proto.id
                                                                            );
                                                                }
                                                            });
                                                    player->del_item_by_serial(key->serial, 1);
                                        		}
                                        	}
                                        }
                                    }

                                    player->att(cs::Pl4s_DayAccPayRewardId, day_reward_idx);
                                    player->att(cs::Pl4s_WeekAccPayRewardId, week_reward_idx);
                                    g_bill->log_order(orderId.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), payfee, "add_to_player");
                                    player->post_save_db([mc, orderId, player, payfee](db_error_t err){
                                        if (err != DBE_Success)
                                            UTIL_LOG(E, "player:%d bill:%f save err ", player->id, payfee);
                                        else {
                                            g_bill->log_order(orderId.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), payfee, "save_to_db");
                                            g_bill->dblog_order(orderId.c_str(), mc.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), payfee, player->create);
                                        }
                                    });

                                    if (charge_times <= 0) {
                                        g_mail->send_system(
                                                player->id,
                                                EMT_ChargeReward,
                                                "",
                                                g_game_cfg->FirstChargeRewardId,
                                                1.0f,
                                                [player](bool ok){
                                                    if (!ok) {
                                                        UTIL_LOG(E,
                                                                "player:%lld, firstchargereward:%d not sended",
                                                                player->id,
                                                                g_game_cfg->FirstChargeRewardId
                                                                );
                                                    }
                                                });
                                    }
                                    ++charge_times;
                                    player->att(Pl4s_ChargeTimes, charge_times);

                                }
                                    break;
                                default:
                                    UTIL_LOG(E,
                                            "err del order:\"order:%d:%s\" from db",
                                            g_cfg->ServerId,
                                            orderId.c_str());
                                    break;
                                }
                            });
                });
    }


}

void handle_busecode(sb::BMsg* msg) {
    const std::string &code = msg->usecode().code();
    int64_t playerid = msg->usecode().playerid();

    auto itr = g_bill->code_map.find(code);
    if (g_bill->code_map.end() == itr) {
        UTIL_LOG(E, "invalid code:%s playerid:%d",
                code.c_str(), playerid);
        return;
    }

    if (g_playermgr->infos.get(playerid) == NULL) {
        UTIL_LOG(E, "has no player code:%s playerid:%d",
                code.c_str(), playerid);
        return;
    }

    if (msg->usecode().errcode() != sb::BUseCode_ErrCode_Success) {
        auto player = g_playermgr->get_player(playerid);
        if (NULL != player) {
            client_session_t* sess = player->sess;
            if (sess) {
                CREATE_CS_SMSG(SUseCode, usecode);
                switch (msg->usecode().errcode()) {
                case sb::BUseCode_ErrCode_NotExist:
                    usecode->set_errcode(cs::SUseCode_ErrCode_Noexist);
                    break;
                case sb::BUseCode_ErrCode_Used:
                    usecode->set_errcode(cs::SUseCode_ErrCode_Used);
                    break;
                case sb::BUseCode_ErrCode_ContractService:
                    usecode->set_errcode(cs::SUseCode_ErrCode_ContractService);
                    break;
                }
                sess->send_msg(___msg);
            }
        }

        return;
    }

    g_bill->used_code.insert(msg->usecode().code());

    int32_t rewardid = itr->second.reward;
    int32_t mask = itr->second.mask;
    bool ret = g_bill->db.del(
            fmt_cstr("code:%s", code.c_str()),
            [mask, code, playerid, rewardid](db_error_t err){
    			g_playermgr->player_load(playerid, [err, mask, code, rewardid, playerid](sp_player_t player){
                    if (player) {
                    	if (mask >= 0)
                    		player->n.code |= (1 << mask);
                        {
                        	uint32_t idmask = player->att(Pl4s_IdMask);
                        	for (int ii=0; ii<g_game_cfg->IdPrefix.size() && ii<32; ++ii) {
                        		if (idmask & (1<<ii))
                        			continue;

                        		if (code.compare(0, g_game_cfg->IdPrefix[ii].size(), g_game_cfg->IdPrefix[ii]) == 0) {
                        			idmask |= (1<<ii);
                        			break;
                        		}
                        	}

                            player->att(Pl4s_IdMask, idmask);

                        	if (player->sess) {
                        		string key = fmt_cstr(
                        				"log:%s:%s:%d-%d",
                        				player->sess->stat.machine_code.c_str(),
                        				player->sess->plat.c_str(),
                        				g_clock->tm_now.tm_year,
                        				g_clock->tm_now.tm_yday
                        				);

                        		if (!g_svr.logoncache.count(key)) {
                        			Json::Value log_json;
                        			log_json["objtype"] = "logon";
                        			log_json["time"] = Json::Int64(g_clock->tt_now);
                        			log_json["idmask"] = player->att(Pl4s_IdMask);
                        			log_json["active"] = Json::Int64(player->sess->active);

                        			g_dbl->set(key.c_str(), log_json);
                        		}
                        	}

                        }
                    }
                    switch (err) {
                    case DBE_Success:
                    {
                        client_session_t* sess = player->sess;
                        if (sess) {
                            CREATE_CS_SMSG(SUseCode, usecode);
                            usecode->set_errcode(cs::SUseCode_ErrCode_Successed);
                            sess->send_msg(___msg);
                        }
                        g_bill->log_code(code.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), "sending mail");
                        g_mail->send_system(
                                playerid,
                                EMT_Code,
                                "",
                                rewardid,
                                1.0f,
                                [code, player](bool ok){
                                    if (!ok) {
                                        UTIL_LOG(E, "mail error code:%s playerid:%d",
                                                code.c_str(), player->id);
                                    } else {
                                        g_bill->log_code(code.c_str(), player->account.c_str(), player->att(Plss_Name).c_str(), "mail sended");
                                    }
                                });
                    }
                        break;
                    default:
                    {
                        UTIL_LOG(E, "mail error code:%s playerid:%d",
                                code.c_str(), playerid);

                        client_session_t* sess = player->sess;
                        if (sess) {
                            CREATE_CS_SMSG(SUseCode, usecode);
                            usecode->set_errcode(cs::SUseCode_ErrCode_ContractService);
                            sess->send_msg(___msg);
                        }
                    }
                        break;
                    }
    			});
            });
}

void send_sreward(const string &account, int32_t rewardid, const string& content, const string &result) {
	if (!g_bill->base::tran.is_connected())
		return;

    CREATE_SB_SMSG(SReward, reward);
    reward->set_account(account);
    reward->set_reward(rewardid);
    reward->set_content(content);
    reward->set_result(result);

    g_bill->base::tran.send(___msg);
}

void handle_breward(sb::BMsg* msg) {
	int64_t playerid = msg->reward().playerid() + co::MaxAccountNum * g_cfg->ServerId;
	int32_t reward = msg->reward().reward();
	const string &content = msg->reward().content();
	const string &account = msg->reward().account();

    if (msg->reward().reward() < 0) {
    	send_sreward(account, reward, content, "no rewardid");
    	return;
    }

    g_mail->send_system(
            playerid,
            EMT_SorryReward,
            msg->reward().content().c_str(),
            msg->reward().reward(),
            1.0f,
            [account, reward, content](bool ok){
                if (!ok) {
                    UTIL_LOG(E, "bill reward mail send err to:%s rewardid:%d", account.c_str(), reward);
                    send_sreward(account, reward, content, "mail error");
                } else {
                	UTIL_LOG(I, "bill reward mail send ok to:%s rewardid:%d", account.c_str(), reward);
                	send_sreward(account, reward, content, "success");
                }
            });

    return ;
}

void handle_bnotice(sb::BMsg* msg) {
	if (!msg->notice().notice().empty())
		g_chat->broad_system(msg->notice().notice().c_str());
}

void bill_session_t::use_code(client_session_t* sess, const string &code) {
    auto player = sess->player;
    if (NULL == player)
        return;

    if (used_code.count(code)) {
        CREATE_CS_SMSG(SUseCode, usecode);
        usecode->set_errcode(SUseCode_ErrCode_Used);
        sess->send_msg(___msg);
        return;
    }

    auto itr = g_bill->code_map.find(code);
    if (itr == g_bill->code_map.end()) {
        CREATE_CS_SMSG(SUseCode, usecode);
        usecode->set_errcode(SUseCode_ErrCode_Noexist);
        sess->send_msg(___msg);
        return;
    }

    if (itr->second.serverid >= 0 && itr->second.serverid != g_cfg->ServerId) {
        CREATE_CS_SMSG(SUseCode, usecode);
        usecode->set_errcode(SUseCode_ErrCode_CouldNotUseInThisServer);
        sess->send_msg(___msg);
        return;
    }

    if (itr->second.mask >= 0 && player->n.code & (1 << itr->second.mask)) {
        CREATE_CS_SMSG(SUseCode, usecode);
        usecode->set_errcode(SUseCode_ErrCode_CouldNotUse);
        sess->send_msg(___msg);
        return;
    }

    if (!base::tran.is_connected()) {
        CREATE_CS_SMSG(SUseCode, usecode);
        usecode->set_errcode(SUseCode_ErrCode_ContractService);
        sess->send_msg(___msg);
        UTIL_LOG(I, "bill_session disconnected while player use code");
        return;
    }

    CREATE_SB_SMSG(SUseCode, usecode);
    usecode->set_account(sess->account);
    usecode->set_playerid(sess->playerid);
    usecode->set_code(code);
    base::tran.send(___msg);
    ping_timer = 4.0f;
}

void bill_session_t::on_create_player(const string &account, const string &playername, int32_t playerid) {
	playerid %= co::MaxAccountNum;
	Json::Value json;
	json["objtype"] = "account";
	json["serverid"] = g_cfg->ServerId;
	json["account"] = account;
	json["playername"] = playername;
	json["playerid"] = playerid;
	db.set( fmt_cstr("account:%d:%s", g_cfg->ServerId, account.c_str()),
			json,
			[account, playername, playerid, this](db_error_t err){
                auto created = new player_created_t;
                created->account = account;
                created->playerid = playerid;
                created->playername = playername;

                need_sync.push_back(created);
			});
}


bool bill_session_t::init() {
	string file_path = fmt_cstr("%s/log/_order_s%d.txt", get_deploy(), g_cfg->ServerId);
    log_fd = open(file_path.c_str(), O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR);
    if (log_fd < 0)
        return false;

    if (!reload_code()) {
    	UTIL_LOG(E, "load code fail");
    	return false;
    }

	bool ok = db.init(g_cfg->BillDbHost.c_str(), "", "", g_cfg->BillDbBucket.c_str());
	if (!ok) {
		UTIL_LOG(E, "bill db init fail");
		return ok;
	}
    ok = ok && base::init(
            fmt_cstr("%s:%d",
                    g_cfg->BillIp.c_str(),
                    g_cfg->BillPort),
            [this](){
                UTIL_LOG(T, "connected");

                CREATE_SB_SMSG(SLogon, logon);
                logon->set_serverid(g_cfg->ServerId);
                base::tran.send(___msg);
                ping_timer = 4.0f;
            },
            [this](){
                authed = false;
            },
            false
            );
    if (!ok) {
        UTIL_LOG(E, "connect to bill at %s:%d failed",
                g_cfg->BillIp.c_str(),
                g_cfg->BillPort);
        return false;
    }
    UTIL_LOG(I, "connect to bill at %s:%d",
            g_cfg->BillIp.c_str(),
            g_cfg->BillPort);

    ok = ok && base::reg(sb::ID_BOrder     ,handle_border     );
    ok = ok && base::reg(sb::ID_BLogon     ,handle_blogon     );
    ok = ok && base::reg(sb::ID_BUseCode   ,handle_busecode   );
    ok = ok && base::reg(sb::ID_BReward    ,handle_breward    );
    ok = ok && base::reg(sb::ID_BNotice    ,handle_bnotice    );

    return ok;
}

void bill_session_t::destroy() {
	base::destroy();
	db.destroy();
    close(log_fd);
}

void bill_session_t::update() {
    if (authed) {
        if (!need_sync.empty()) {
            CREATE_SB_SMSG(SCreatePlayer, createplayer);
            for (int i=0; i<5; ++i) {
                if (need_sync.empty())
                    break;

                auto tosync = need_sync.front();
                need_sync.pop_front();
                auto created = createplayer->add_createplayer();
                created->set_account(tosync->account);
                created->set_playername(tosync->playername);
                created->set_playerid(tosync->playerid);
            }
            g_bill->tran.send(___msg);
            ping_timer = 4.0f;
        }
        ping_timer -= g_clock->delta;
        if (ping_timer <= 0.0f) {
        	CREATE_SB_SMSG(SPing, ping);
        	g_bill->tran.send(___msg);
            ping_timer = 4.0f;
        }
    }

    base::update();
    db.update();
}

void bill_session_t::log_order(const char* orderid, const char* account, const char* player, double fee, const char* status) {
	char buff[100];
	time_t now = time(NULL);
	strftime(buff,100,"%Y-%m-%d_%H:%M:%S",localtime(&now));
	const char* tmp_str = fmt_cstr("%s orderid:%s account:%s fee:%f %s\n",
			buff, orderid, account, fee, status);
	int res = write(log_fd, tmp_str, strlen(tmp_str));
	assert(res >= 0);
	if (res < 0) {
		UTIL_LOG(E, "error when write bill log: \"%s\"\n", tmp_str);
	}
}

void bill_session_t::log_code(const char* code, const char* account, const char* player, const char* status) {
	char buff[100];
	time_t now = time(NULL);
	strftime(buff,100,"%Y-%m-%d_%H:%M:%S",localtime(&now));
	const char* tmp_str = fmt_cstr("%s code:%s account:%s %s\n",
			buff, code, account, status);
	int res = write(log_fd, tmp_str, strlen(tmp_str));
	assert(res >= 0);
	if (res < 0) {
		UTIL_LOG(E, "error when write bill log: \"%s\"\n", tmp_str);
	}
}

void bill_session_t::dblog_order(const char* orderid, const char* mc, const char* account, const char* player, double fee, time_t create_time) {
	if (!g_dbl->ok()) {
		return;
	}
	Json::Value json;
	json["objtype"] = "order";

	json["orderid"] = orderid;
	json["mc"] = mc;
	json["account"] = account;
	json["player"] = player;
	json["fee"] = fee;
    json["date"] = Json::Int64(g_clock->tt_now);
    json["create"] = Json::Int64(create_time);

    string key = fmt_cstr("order:%s:%lld", account, g_clock->tt_now);
    g_dbl->set(key.c_str(), json, [key](db_error_t err){
        if (DBE_Success != err)
            UTIL_LOG(E, "err save bill_log %S", key.c_str());
    });
}

bool bill_session_t::reload_code() {
	used_code.clear();

    std::string cfg_path = fmt_cstr("%s/cfg/code.txt", get_deploy());
    Json::Value cfg;
    const char* cfg_file = load_file(cfg_path.c_str());
    if (NULL != cfg_file)
        cfg = to_json(cfg_file);

    const Json::Value &codes = cfg["item"];
    if (!codes.isArray()) {
        UTIL_LOG(E, "code file empty");
        return false;
    }

    for (auto itr = codes.begin(); itr != codes.end(); ++itr) {
        code_proto_t proto;
        proto.code = (*itr).get("code", "").asCString();
        proto.reward = (*itr).get("reward", -1).asInt();
        proto.mask = (*itr).get("mask", -1).asInt();
        proto.serverid = (*itr).get("serverid", -1).asInt();

        if (MAProto(Reward, proto.reward) == NULL) {
            UTIL_LOG(E, "code:%s reward:%d invalid", proto.code.c_str(), proto.reward);
            return false;
        }

        code_map.insert(make_pair(proto.code, proto));
    }

    return true;
}
